<template>
    <div>
        <el-row>
            <el-select v-model="type" placeholder="题型">
                <el-option
                    v-for="item in types"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value" />
            </el-select>
        </el-row>
        <el-row>
            <input ref="excel-upload-input"
                   class="excel-upload-input"
                   type="file"
                   :accept="excelType"
                   @change="handleClick"/>
            <el-button @click="handleUpload">选择文件</el-button>
        </el-row>
        <el-row>
            <el-table :data="sets">
                <el-table-column prop="set" label="集合" />
                <el-table-column label="添加到">
                    <template v-slot="scope">
                        <el-container>
                            <el-select v-model="course[scope.row.set]" placeholder="课程">
                                <el-option
                                    v-for="item in courses"
                                    :key="item.value"
                                    :label="item.label"
                                    :value="item.value" />
                            </el-select>
                            <el-select v-model="unit[scope.row.set]" placeholder="单元">
                                <el-option-group
                                    v-for="group in units"
                                    :key="group.value"
                                    :label="group.label">
                                    <el-option
                                        v-for="item in group.options"
                                        :key="item.value"
                                        :value="item.value"
                                        :label="item.label" />
                                </el-option-group>
                            </el-select>
                        </el-container>
                    </template>
                </el-table-column>
            </el-table>
        </el-row>
        <el-row>
            <el-button type="primary" @click="handleSubmit">批量添加</el-button>
        </el-row>
    </div>
</template>

<script>
    import xlsx from 'xlsx';
    export default {
        name: "Excel",
        data() {
            return {
                excelType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                sets: [],
                courses: [{
                    value: 0,
                    label: 'Test Course',
                }],
                units: [{
                    value: 0,
                    label: 'Test Tour 1',
                    options: [{
                        value: 0,
                        label: 'Test Unit 1',
                    }]
                }, {
                    value: 1,
                    label: 'Test Tour 2',
                    options: [{
                        value: 1,
                        label: 'Test Unit 2',
                    }, {
                        value: 2,
                        label: 'Test Unit 3',
                    }]
                }],
                types: [
                    { value: 0, label: 'A_GIVEN_B_BLANK' },
                    { value: 1, label: 'A_GIVEN_B_CLOZE' },
                    { value: 2, label: 'A_CLOZE_B_GIVEN' },
                    { value: 3, label: 'UNSCRAMBLE' },
                    { value: 4, label: 'LETTER_NUMBER_MATCH' },
                    { value: 5, label: 'MULTIPLE_CHOICE' },
                    { value: 6, label: 'MATCHUPCLICK' },
                ],
                course: {},
                unit: {},
                type: null,
            };
        },
        methods: {
            handleUpload() {
                this.$refs['excel-upload-input'].click();
            },
            addToSet(data, value) {
                const set = value.set || value.Set;
                if (set === null || typeof set === 'undefined') throw new Error();
                if (!data[set]) {
                    data[set] = {};
                    this.sets.push({ set });
                }
                return set;
            },
            decodeHidden(string) {
                const start = string.indexOf('[f]');
                if (start === -1) throw new Error();
                const end = string.indexOf('[-f]', start + 1);
                if (end === -1) throw new Error();

                const a = string.substring(0, start);
                if (start + 3 === end) throw new Error();
                const b = string.substring(start + 3, end);
                let c;
                let hint = undefined;
                if (string[end + 4] === '(') {
                    const r = string.indexOf(')', end + 5);
                    if (r === -1) throw new Error();

                    c = string.substring(r + 1);
                    hint = string.substring(end + 5, r);
                } else {
                    c = string.substring(end + 4);
                }
                return [
                    a,
                    { hint, answers: [ b ] },
                    c,
                ];
            },
            decodeChoices(string) {
                const question = [];
                let index = 0;
                let start, end;
                const indices = [];
                // eslint-disable-next-line no-constant-condition
                while (true) {
                    start = string.indexOf('[f]', index);
                    if (start === -1) break;
                    end = string.indexOf('[-f]', start + 3);
                    if (end === -1) throw new Error();
                    if (index !== start) {
                        question.push(string.substring(index, start));
                    }
                    if (start + 3 === end) throw new Error();
                    indices.push(question.length);
                    question.push({
                        choices: [ string.substring(start + 3, end) ],
                        answer_index: 0
                    });
                    index = end + 4;
                }
                if (indices.length === 0) throw new Error();
                if (index !== string.length) {
                    question.push(string.substring(index));
                }
                return { question, indices };
            },
            a_cloze_b_given(json, data) {
                json.forEach(value => {
                    const set = this.addToSet(data, value);
                    const idx = value.question || value.Question;
                    if (idx === null || typeof idx === 'undefined') throw new Error();
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    const question = [];
                    let narration = value.a || value.A;
                    if (typeof narration !== 'string') throw new Error();
                    narration = this.decodeHidden(narration);
                    for (const name in value) {
                        if (!Object.prototype.hasOwnProperty.call(value, name)) continue;
                        if (name.indexOf('a alternative ') === 0
                            || name.indexOf('A alternative ') === 0) {
                            const start = value[name].indexOf('[f]');
                            if (start === -1) throw new Error();
                            const end = value[name].indexOf('[-f]');
                            if (end === -1) throw new Error();
                            narration[1].answers.push(value[name].substring(start + 3, end));
                        }
                    }
                    if (narration[2] === '') narration.pop();
                    if (narration[0] === '') narration.shift();
                    question.push({
                        narrator: 'A',
                        text: narration,
                    });
                    narration = value.b || value.B;
                    if (typeof narration !== 'string') throw new Error();
                    question.push({
                        narrator: 'B',
                        text: [ narration ],
                    });
                    data[set][idx] = { type: 1, comment, question };
                });
            },
            a_given_b_cloze(json, data) {
                json.forEach(value => {
                    const set = this.addToSet(data, value);
                    const idx = value.question || value.Question;
                    if (idx === null || typeof idx === 'undefined') throw new Error();
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    const question = [];
                    let narration = value.a || value.A;
                    if (typeof narration !== 'string') throw new Error();
                    question.push({
                        narrator: 'A',
                        text: [ narration ],
                    });
                    narration = value.b || value.B;
                    if (typeof narration !== 'string') throw new Error();
                    narration = this.decodeHidden(narration);
                    for (const name in value) {
                        if (!Object.prototype.hasOwnProperty.call(value, name)) continue;
                        if (name.indexOf('b alternative ') === 0
                            || name.indexOf('B alternative ') === 0) {
                            const start = value[name].indexOf('[f]');
                            if (start === -1) throw new Error();
                            const end = value[name].indexOf('[-f]');
                            if (end === -1) throw new Error();
                            narration[1].answers.push(value[name].substring(start + 3, end));
                        }
                    }
                    if (narration[2] === '') narration.pop();
                    if (narration[0] === '') narration.shift();
                    question.push({
                        narrator: 'B',
                        text: narration,
                    });
                    data[set][idx] = { type: 1, comment, question };
                });
            },
            a_given_b_blank(json, data) {
                json.forEach(value => {
                    const set = this.addToSet(data, value);
                    const idx = value.question || value.Question;
                    if (idx === null || typeof idx === 'undefined') throw new Error();
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    const question = value.a || value.A;
                    if (typeof question !== 'string') throw new Error();
                    const answers = [];
                    for (const name in value) {
                        if (!Object.prototype.hasOwnProperty.call(value, name)) continue;
                        if (name === 'b'
                            || name === 'B'
                            || name.indexOf('b alternative ') === 0
                            || name.indexOf('B alternative ') === 0) {
                            answers.push(value[name]);
                        }
                    }
                    data[set][idx] = { type: 0, comment, question, answers };
                });
            },
            unscramble(json, data) {
                json.forEach(value => {
                    const set = this.addToSet(data, value);
                    const idx = value.question || value.Question;
                    if (idx === null || typeof idx === 'undefined') throw new Error();
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    const question = [];
                    const top = value.top || value.Top || value.TOP;
                    if (top) question.push(top);
                    const unscramble = [];
                    for (let i = 1;; i++) {
                        const item = value['u' + i.toString()] || value['U' + i.toString()];
                        if (item) {
                            unscramble.push(item);
                        } else break;
                    }
                    question.push({ unscramble });
                    const bottom = value.bottom || value.Bottom || value.BOTTOM;
                    if (bottom) question.push(bottom);
                    data[set][idx] = { type: 2, comment, question };
                });
            },
            multiple_choice(json, data) {
                json.forEach(value => {
                    const set = this.addToSet(data, value);
                    const idx = value.question || value.Question;
                    if (idx === null || typeof idx === 'undefined') throw new Error();
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    const sentence = value['MAIN SENTENCE'] || value['Main sentence'] || value['main sentence'];
                    if (typeof sentence !== 'string') throw new Error();
                    const { question, indices } = this.decodeChoices(sentence);
                    for (let i = 1; i <= indices.length; i++) {
                        for (let j = 1;; j++) {
                            const item = value['Blank' + i.toString() + 'H' + j.toString()]
                                || value['blank' + i.toString() + 'h' + j.toString()];
                            if (typeof item !== 'string') {
                                if (j !== 1) break;
                                else throw new Error();
                            }
                            question[indices[i - 1]].choices.push(item);
                        }
                    }
                    data[set][idx] = { type: 4, comment, question };
                })
            },
            letter_number_match(json, data) {
                for (const value of json) {
                    const set = this.addToSet(data, value);
                    if (data[set].type === null || typeof data[set].type === 'undefined') data[set].type = 3;
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    if (typeof data[set].comment !== 'string') data[set].comment = comment;
                    if (!data[set].question) data[set].question = [];

                    const item = [];
                    for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) {
                        const str = value[String.fromCharCode(i)] || value[String.fromCharCode(i - 'A'.charCodeAt(0) + 'a'.charCodeAt(0))];
                        if (str) item.push(str);
                        else break;
                    }
                    data[set].question.push(item);
                }
                for (const item in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, item)) continue;
                    let length = null;
                    for (const arr of data[item].question) {
                        if (length === null) length = arr.length;
                        else if (length !== arr.length) throw new Error();
                    }
                }
            },
            matchupclick(json, data) {
                for (const value of json) {
                    const set = this.addToSet(data, value);
                    if (data[set].type === null || typeof data[set].type === 'undefined') data[set].type = 5;
                    const comment = value.comment || value.Comment;
                    if (typeof comment !== 'string') throw new Error();
                    if (typeof data[set].comment !== 'string') data[set].comment = comment;
                    if (!data[set].question) data[set].question = [];
                    if (!data[set].box) data[set].box = new Set();

                    const given = value.GIVEN || value.Given || value.given;
                    if (typeof given !== 'string') throw new Error();
                    const answer = value.DESIRED || value.Desired || value.desired;
                    if (typeof answer !== 'string') throw new Error();
                    data[set].question.push({
                        given,
                        answers: [ answer ],
                    });

                    for (let i = 1;; i++) {
                        const s = (i < 10) ? '0' : '';
                        const item = value['PIECE-' + s + i.toString()]
                            || value['Piece-' + s + i.toString()]
                            || value['piece-' + s + i.toString()]
                            || value['PIECE-' + i.toString()]
                            || value['Piece-' + i.toString()]
                            || value['piece-' + i.toString()];
                        if (typeof item !== 'string') break;
                        data[set].box.add(item);
                    }
                }
                for (const i in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, i)) continue;
                    data[i].box = Array.from(data[i].box);
                }
            },
            questionsFromJson(json) {
                const data = {};
                this.sets = [];
                switch (this.type) {
                    case 0:
                        this.a_given_b_blank(json, data);
                        break;
                    case 1:
                        this.a_given_b_cloze(json, data);
                        break;
                    case 2:
                        this.a_cloze_b_given(json, data);
                        break;
                    case 3:
                        this.unscramble(json, data);
                        break;
                    case 4:
                        this.letter_number_match(json, data);
                        break;
                    case 5:
                        this.multiple_choice(json, data);
                        break;
                    case 6:
                        this.matchupclick(json, data);
                        break;
                    default:
                        this.$message.error('未选择题型');
                        break;
                }
                console.log(data);
                return data;
            },
            handleSubmit() {
                this.$alert('Replace with your implementation').catch(() => {});
            },
            upload(file) {
                if (file.type !== this.excelType) {
                    this.$message.error('仅支持 xlsx 文件');
                }
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onload = ev => {
                        const data = ev.target.result;
                        const workbook = xlsx.read(data, { type: 'array' });
                        const firstSheetName = workbook.SheetNames[0];
                        const workSheet = workbook.Sheets[firstSheetName];
                        const json = xlsx.utils.sheet_to_json(workSheet);
                        try {
                            // const question =
                            this.questionsFromJson(json);
                            resolve();
                        } catch (e) {
                            this.sets = [];
                            console.log(e);
                            reject(e);
                        }
                    };
                    reader.readAsArrayBuffer(file);
                });
            },
            handleClick(e) {
                const files = e.target.files;
                const rawFile = files[0];
                this.$refs['excel-upload-input'].value = null;
                if (rawFile) this.upload(rawFile).catch(() => this.$message.error('操作失败'));
            },
        }
    }
</script>

<style scoped>
    .excel-upload-input {
        display: none;
    }
</style>
